BOKOSCU

Работа с файлами в Node.js: fs/promises и потоки

Node.js предоставляет богатые возможности для работы с файловой системой. В этой записи будет два ключевых подхода: обещания (fs/promises) и потоки (Streams), с особым акцентом на потоках как наиболее эффективном способе работы с большими файлами.

1. fs/promises: работа с файлами в стиле async/await

Модуль fs/promises предоставляет promise-версии всех основных методов модуля fs. Это упрощает код и делает его более читаемым.

Пример: чтение и запись файла

async function copyFile() {
  const data = await readFile('source.txt', 'utf-8');
  await writeFile('destination.txt', data);
}

Этот подход хорош для небольших файлов. Но с большими файлами возникают проблемы: файл читается/записывается целиком в память, что может привести к утечкам памяти и тормозам. Прикинь загружать текст размером 8 гигов с ограничением VDS в 4? Вот за этим стримы и нужны.

2. Потоки (Streams): эффективная обработка данных

Потоки позволяют обрабатывать данные по частям, не загружая всё в память. В Node.js есть четыре основных типа потоков:

Readable — читающий поток (например, чтение из файла)
Writable — записывающий поток (например, запись в файл)
Duplex — двунаправленный (и чтение, и запись)
Transform — как Duplex, но с преобразованием данных на лету

Чтение и запись файлов через потоки

import { createReadStream, createWriteStream } from 'fs';

const readable = createReadStream('bigfile.txt');
const writable = createWriteStream('copy.txt');

readable.pipe(writable);

Метод pipe передаёт данные из одного потока в другой. Это самый эффективный способ копировать большие файлы. Можно вручную читать и записывать из одного потока в другой, даже делать при этом приобразования, но такой метод чуть менее эффективнее, чем трансформация и pipe.
P.s. не делай так, как я описал выше.

Управление потоком вручную

Иногда нужно обрабатывать данные между чтением и записью:

import { createReadStream, createWriteStream } from 'fs';

const readStream = createReadStream('input.txt', { encoding: 'utf-8' });
const writeStream = createWriteStream('output.txt');

readStream.on('data', (chunk) => {
  const upper = chunk.toUpperCase();
  writeStream.write(upper);
});

readStream.on('end', () => {
  writeStream.end();
});

Но такой способ не очень эффективен сам по себе. Вот лучше:

Использование Transform потока

import { Transform } from 'stream';

const upperCaseTransform = new Transform({
  transform(chunk, encoding, callback) {
    const transformed = chunk.toString().toUpperCase();
    callback(null, transformed);
  }
});

И подключение его к цепочке:

import { createReadStream, createWriteStream } from 'fs';

createReadStream('input.txt')
  .pipe(upperCaseTransform)
  .pipe(createWriteStream('output.txt'));

Обработка ошибок

Знаю, ты прогер от бога и ошибок не делаешь, но на всякий случай:

readStream.on('error', console.error);
writeStream.on('error', console.error);

Когда использовать что?

Метод Подходит для
fs/promises Небольшие файлы, простой код
Потоки Большие файлы, обработка на лету

Если кто-то желает присоединиться - добро пожаловать.
-Jame